#ifndef SEEDLING_DEFINES_H
#define SEEDLING_DEFINES_H

#include <vector>

/* Type definitions for convenience */
#define coordinate std::pair<int, int>
#define range std::pair<int, int>
#define SeedlingSegmentVector std::vector<SeedlingSegment*>
#define INFLECTION_THRESHOLD 5

enum DIRECTION {
	NORTH,
	NORTH_EAST,
	EAST,
	SOUTH_EAST,
	SOUTH,
	SOUTH_WEST,
	WEST,
	NORTH_WEST
};

class SeedlingSegment {
public:
    unsigned int _segmentGlobalId;

	SeedlingSegment(int x, int y) 
		: _endDirection(NORTH), _startDirection(NORTH), _overallDirection(NORTH), _hasInflectionPoint(false)
	{		
		_increasingPixels = 0;
		_decreasingPixels = 0;
		_segment.push_back(std::make_pair(x, y));
		_startCoordinate = std::make_pair(x, y);
	}

	SeedlingSegment() 
		: _endDirection(NORTH), _startDirection(NORTH), _overallDirection(NORTH), _hasInflectionPoint(false)
	{
		_increasingPixels = 0;
		_decreasingPixels = 0;
	}

	SeedlingSegment& operator=(SeedlingSegment &ss) {
		this->setStartDirection(ss.getStartDirection());
		this->setEndDirection(ss.getEndDirection());
		this->setSegment(ss.getSegment());
		return *this;
	}

	~SeedlingSegment(void) {
	}

	std::pair<int, int> &getMaxCoordinate() {
		return _maxCoordinate;
	}

	std::pair<int, int> &getMinCoordinate() {
		return _minCoordinate;
	}

	std::vector<coordinate> &getSegment(void) {
		return _segment;
	}

	void setSegment(std::vector<coordinate> segment) {
		_segment.clear();
		_segment.assign(segment.begin(), segment.end());
	}

	void addToSegment(int x, int y) {
		if(_segment.empty()) {
			_startCoordinate = std::make_pair(x,y);
			_maxCoordinate = std::make_pair(x,y);
			_minCoordinate = std::make_pair(0,0);
		}
		else if(_maxCoordinate.second >= y) {
			_maxCoordinate.first = x;
			_maxCoordinate.second = y;
			_increasingPixels++;
			_decreasingPixels = 0;
		}
		else {
			_decreasingPixels++;
		}
		if(_minCoordinate.second < y) {
			_minCoordinate.first = x;
			_minCoordinate.second = y;
		}
		_segment.push_back(std::make_pair(x, y));
	}

	bool hasInflectionPoint() {
		return _hasInflectionPoint;
	}
	
	void checkForInflection() {
		if(_increasingPixels > _decreasingPixels && _decreasingPixels > INFLECTION_THRESHOLD) {
			_hasInflectionPoint = true;
		}
	}

	DIRECTION getEndDirection(void) {
		return _endDirection;
	}
	
	DIRECTION getStartDirection(void) {
		return _endDirection;
	}

	DIRECTION getOverallDirection(void) {
		return _overallDirection;
	}

	coordinate *getStartCoordinate() {
		return &_startCoordinate;
	}

	void setEndDirection(DIRECTION d) {
		_endDirection = d;
	}

	void setStartDirection(DIRECTION d) {
		_startDirection = d;
	}

	void setStartCoordinate(coordinate &startCoordinate) {
		_startCoordinate.first = startCoordinate.first;
		_startCoordinate.second = startCoordinate.second;
	}
	
	void mergeSeedlingSegment(SeedlingSegment* segmentToMerge) {
		_segment.reserve(_segment.size() + segmentToMerge->getSegment().size());
		std::vector<coordinate> mergingSegment = segmentToMerge->getSegment();
		for(unsigned int i = 0; i < mergingSegment.size(); i++) {
			_segment.push_back(mergingSegment.at(i));
		}
		this->_endDirection = segmentToMerge->getEndDirection();
	}
	
	void updateStartDirection() {
		unsigned short directions[8] = {0};
		unsigned int i = 20 < _segment.size() ? 20 : _segment.size();
		coordinate *current, *previous;
		for(unsigned int j = 1; j < i; j++) {
			current = &(_segment[j]);
			previous = &(_segment[j - 1]);

			if(current->first == previous->first && current->second == previous->second - 1) {
				directions[NORTH]++;
			}
			else if(current->first == previous->first + 1 && current->second == previous->second + 1) {
				directions[NORTH_EAST]++;
			}
			else if(current->first == previous->first + 1 && current->second == previous->second) {
				directions[EAST]++;
			}
			else if(current->first == previous->first + 1 && current->second == previous->second + 1) {
				directions[SOUTH_EAST]++;
			}
			else if(current->first == previous->first && current->second == previous->second + 1) {
				directions[SOUTH]++;
			}
			else if(current->first == previous->first - 1 && current->second == previous->second + 1) {
				directions[SOUTH_WEST]++;
			}
			else if(current->first == previous->first - 1 && current->second == previous->second) {
				directions[WEST]++;
			}
			else if(current->first == previous->first - 1 && current->second == previous->second - 1) {
				directions[NORTH_WEST]++;
			}
		}

		unsigned short maximum = 0;
		for(unsigned int j = NORTH; j <= NORTH_WEST; j++) {
			if(maximum < directions[j]) {
				_startDirection = (DIRECTION)j;
				maximum = directions[j];
			}
		}

		if(_startDirection == NORTH) {
			int eastPixelsCount = directions[EAST] + directions[NORTH_EAST];
			int westPixelsCount = directions[WEST] + directions[NORTH_WEST];
			if(eastPixelsCount > westPixelsCount && eastPixelsCount > 5) {
				_startDirection = NORTH_EAST;
			}
			else if(westPixelsCount > eastPixelsCount && westPixelsCount > 5) {
				_startDirection = NORTH_WEST;
			}
		}
	}

	void updateEndDirection() {
		unsigned short directions[8] = {0};
		int i = _segment.size() - 20;
		i = i > 0 ? i : 1;
		coordinate *current, *previous;
		for(unsigned int j = i; j < _segment.size(); j++) {
			current = &(_segment[j]);
			previous = &(_segment[j - 1]);

			if(current->first == previous->first && current->second == previous->second - 1) {
				directions[NORTH]++;
			}
			else if(current->first == previous->first + 1 && current->second == previous->second + 1) {
				directions[NORTH_EAST]++;
			}
			else if(current->first == previous->first + 1 && current->second == previous->second) {
				directions[EAST]++;
			}
			else if(current->first == previous->first + 1 && current->second == previous->second + 1) {
				directions[SOUTH_EAST]++;
			}
			else if(current->first == previous->first && current->second == previous->second + 1) {
				directions[SOUTH]++;
			}
			else if(current->first == previous->first - 1 && current->second == previous->second + 1) {
				directions[SOUTH_WEST]++;
			}
			else if(current->first == previous->first - 1 && current->second == previous->second) {
				directions[WEST]++;
			}
			else if(current->first == previous->first - 1 && current->second == previous->second - 1) {
				directions[NORTH_WEST]++;
			}
		}

		unsigned short maximum = 0;
		for(unsigned int j = NORTH; j <= NORTH_WEST; j++) {
			if(maximum < directions[j]) {
				_endDirection = (DIRECTION)j;
				maximum = directions[j];
			}
		}

		if(_endDirection == NORTH) {
			int eastPixelsCount = directions[EAST] + directions[NORTH_EAST];
			int westPixelsCount = directions[WEST] + directions[NORTH_WEST];
			if(eastPixelsCount > westPixelsCount && eastPixelsCount > 2) {
				_endDirection = NORTH_EAST;
			}
			else if(westPixelsCount > eastPixelsCount && westPixelsCount > 2) {
				_endDirection = NORTH_WEST;
			}
		}
	}

	void updateOverallDirection() {
		coordinate *start = &(_segment.front()), *end = &(_segment.back());

		if(start->first == end->first && start->second == end->second - 1) {
			_overallDirection = NORTH;
		}
		else if(start->first == end->first + 1 && start->second == end->second + 1) {
			_overallDirection = NORTH_EAST;
		}
		else if(start->first == end->first + 1 && start->second == end->second) {
			_overallDirection = EAST;
		}
		else if(start->first == end->first + 1 && start->second == end->second + 1) {
			_overallDirection = SOUTH_EAST;
		}
		else if(start->first == end->first && start->second == end->second + 1) {
			_overallDirection = SOUTH;
		}
		else if(start->first == end->first - 1 && start->second == end->second + 1) {
			_overallDirection = SOUTH_WEST;
		}
		else if(start->first == end->first - 1 && start->second == end->second) {
			_overallDirection = WEST;
		}
		else if(start->first == end->first - 1 && start->second == end->second - 1) {
			_overallDirection = NORTH_WEST;
		}
	}

private:
	int _increasingPixels, _decreasingPixels;
	DIRECTION _endDirection, _startDirection, _overallDirection;
	coordinate _startCoordinate, _maxCoordinate, _minCoordinate;
	std::vector<coordinate> _segment;
	bool _hasInflectionPoint;
};

/***************************************************************************************************
 ***************************************************************************************************
 ********************************** END OF SEEDLING SEGMENT CLASS **********************************
 ***************************************************************************************************
 ***************************************************************************************************/

class CompleteSeedling {
public:
	CompleteSeedling() : _rootSize(0) {
	}
	
	~CompleteSeedling() {
		for(unsigned int i = 0; i < secondaryRoots.size(); i++) {
			delete secondaryRoots[i];
		}
		for(unsigned int i = 0; i < segmentsInPrimaryRoot.size(); i++) {
			delete segmentsInPrimaryRoot[i];
		}
	}

	bool isSegmentDirectionCompatible(SeedlingSegment* segment) {
		bool isCompatible = false;
		DIRECTION segmentStartDir = segment->getOverallDirection();
		switch(segmentsInPrimaryRoot[0]->getEndDirection()) {
			case NORTH:
				switch(segmentStartDir) {
					case NORTH:
						isCompatible = true;
						break;
					case NORTH_EAST:
						isCompatible = true;
						break;
					case NORTH_WEST:
						isCompatible = true;
						break;
					case EAST:
						isCompatible = true;
						break;
					case WEST:
						isCompatible = true;
						break;
					default:
						isCompatible = false;
						break;
				}
				break;
			case NORTH_EAST:
				switch(segmentStartDir) {
					case NORTH:
						isCompatible = true;
						break;
					case NORTH_EAST:
						isCompatible = true;
						break;
					case EAST:
						isCompatible = true;
						break;
					default:
						isCompatible = false;
						break;
				}
				break;
			case EAST:
				switch(segmentStartDir) {
					case NORTH:
						isCompatible = true;
						break;
					case NORTH_EAST:
						isCompatible = true;
						break;
					case EAST:
						isCompatible = true;
						break;
					default:
						isCompatible = false;
						break;
				}
				break;
			case WEST:
				switch(segmentStartDir) {
					case NORTH:
						isCompatible = true;
						break;
					case NORTH_WEST:
						isCompatible = true;
						break;
					case WEST:
						isCompatible = true;
						break;
					default:
						isCompatible = false;
						break;
				}
				break;
			case NORTH_WEST:
				switch(segmentStartDir) {
					case NORTH:
						isCompatible = true;
						break;
					case NORTH_WEST:
						isCompatible = true;
						break;
					case WEST:
						isCompatible = true;
						break;
					default:
						isCompatible = false;
						break;
				}
				break;
			default:
				isCompatible = false;
				break;
		}
		return isCompatible;
	}
	
	void addToPrimaryRoot(SeedlingSegment* segment) {
		if(!segmentsInPrimaryRoot.empty() && segmentsInPrimaryRoot.back()->hasInflectionPoint()) {
			return;
		}
		_rootSize += segment->getSegment().size();
		segmentsInPrimaryRoot.push_back(segment);
	}

    unsigned int getRootLength() {
        return _rootSize;
    }

	void printSeedlingInfo() {
		SeedlingSegment *first = segmentsInPrimaryRoot.front(), *last = segmentsInPrimaryRoot.back();
		std::cout << "Seedling Starting Coordinate: ( " 
				  << first->getSegment()[0].first << ", "
				  << first->getSegment()[0].second
				  << " )" << std::endl;
		std::cout << "Seedling Ending Coordinate: ( " 
				  << last->getSegment()[0].first << ", "
				  << last->getSegment()[0].second
				  << " )" << std::endl;
		std::cout << "Seedling Length (pixels): " << _rootSize << std::endl << std::endl;
		std::cout << "Number of Secondary Roots: " << secondaryRoots.size() << std::endl;
		for(unsigned int i = 0; i < secondaryRoots.size(); i++) {
			std::cout << "Secondary Root #" << i << ":" << std::endl;
			std::cout << "Start Coordinate: ( "
					  << secondaryRoots[i]->getStartCoordinate()->first << ", "
					  << secondaryRoots[i]->getStartCoordinate()->second << " )"
					  << std::endl;
			std::cout << "End Coordinate: ( "
					  << secondaryRoots[i]->getSegment().back().first << ", "
					  << secondaryRoots[i]->getSegment().back().second << " )"
					  << std::endl;
			std::cout << "Size: " << secondaryRoots[i]->getSegment().size() << std::endl;
		}
	}

	size_t numberOfRoots() {
		return segmentsInPrimaryRoot.size();
	}

	void addAsSecondaryRoot(SeedlingSegment *segment) {
		secondaryRoots.push_back(segment);
	}

	bool isPartOfSeedling(SeedlingSegment *segment) {
		bool isSecondaryRoot = false, isPartOfPrimaryRoot = false;

		// Check segments considered secondary roots of this seedling
		for(unsigned int i = 0; i < secondaryRoots.size(); i++) {
			// Quick pointer check to see if the segment and secondaryRoot element pointers point to the same object
			if(segment == secondaryRoots[i]) {
				isSecondaryRoot = true;
				break;
			}
			std::vector<coordinate> *curSecondaryRoot = &(secondaryRoots[i]->getSegment());
			if(curSecondaryRoot->size() > 0) {
				if( curSecondaryRoot->front().first == segment->getSegment().front().first &&
					curSecondaryRoot->front().second == segment->getSegment().front().second &&
					curSecondaryRoot->back().first == segment->getSegment().back().first &&
					curSecondaryRoot->back().second == segment->getSegment().back().second) {

					isSecondaryRoot = true;
					break;
				}
			}
		}

		for(unsigned int i = 0; i < segmentsInPrimaryRoot.size(); i++) {
			// Quick pointer check to see if the segment and secondaryRoot element pointers point to the same object
			if(segment == segmentsInPrimaryRoot[i]) {
				isPartOfPrimaryRoot = true;
				break;
			}
			std::vector<coordinate> *curPrimarySegment = &(segmentsInPrimaryRoot[i]->getSegment());
			if(curPrimarySegment->size() > 0) {
				if( curPrimarySegment->front().first == segment->getSegment().front().first &&
					curPrimarySegment->front().second == segment->getSegment().front().second &&
					curPrimarySegment->back().first == segment->getSegment().back().first &&
					curPrimarySegment->back().second == segment->getSegment().back().second &&
					curPrimarySegment->size() == segment->getSegment().size()) {

					isPartOfPrimaryRoot = true;
					break;
				}
			}
		}
		return isPartOfPrimaryRoot || isSecondaryRoot;
	}

	SeedlingSegment* getRootSegmentAt(int index) {
		if(index < 0) {
			int i = segmentsInPrimaryRoot.size() + index;
			i = i >= 0 ? i : 0;
			return segmentsInPrimaryRoot[i];
		}
		else {
			return segmentsInPrimaryRoot[index];
		}
	}

private:
	unsigned int _rootSize;
	SeedlingSegmentVector secondaryRoots;
	SeedlingSegmentVector segmentsInPrimaryRoot;
};

#endif //  SEEDLING_DEFINES_H
